package com.hframe.log;

import com.hframe.data.Base;
import com.hframe.data.DRef;
import com.hframe.type.TArray;
import com.hframe.type.TData;
import com.hframe.type.TString;
import com.sun.istack.internal.NotNull;
import org.apache.log4j.Logger;

import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;


/**
 * 日志对比器
 * Created by heqian on 17-9-12.
 */
public class UseLog<T extends Base> {
    private static Logger log = Logger.getLogger(UseLog.class);
    protected Storage<T> mStorage;
    protected Result mResult = null;
    private Object mData = null;
    private Date mDateTime;
    private Enum mUseEnum = null;

    /**
     * 创建使用日志对比器
     *
     * @param data    备用数据
     * @param useEnum 数据存储器
     */
    public UseLog(Object data, @NotNull Enum useEnum) {
        this.mData = data;
        this.mUseEnum = useEnum;
    }

    /**
     * 创建使用日志对比器
     *
     * @param data    备用数据
     * @param useEnum 操作类型
     * @param storage 数据存储器
     */
    public UseLog(Object data, @NotNull Enum useEnum, Storage<T> storage) {
        this.mData = data;
        this.mStorage = storage;
        this.mUseEnum = useEnum;
    }

    /**
     * 创建使用日志对比器
     *
     * @param data     备用数据
     * @param useEnum  操作类型
     * @param name     添加自定义结果
     * @param newValue
     */
    public UseLog(Object data, @NotNull Enum useEnum, @NotNull String name, Object newValue) {
        this.mData = data;
        this.mUseEnum = useEnum;
        this.mResult = new Result(newValue);
        this.mResult.name.set(name);
    }

    /**
     * 获得操作类型
     *
     * @return
     */
    public Enum getUseEnum() {
        return mUseEnum;
    }

    /**
     * 获得操作结果
     *
     * @return
     */
    public Result getResult() {
        return mResult;
    }


    /**
     * 获得备用数据
     *
     * @return
     */
    public Object getData() {
        return mData;
    }

    /**
     * 设置操作类型
     *
     * @param useEnum
     */
    public void setUseEnum(@NotNull Enum useEnum) {
        this.mUseEnum = useEnum;
    }

    /**
     * 对比数据
     *
     * @param obj
     * @return
     */
    public UseLog<T> compare(String name, T obj) throws Exception {
        if (null != mStorage && null != obj) {
            T src = mStorage.getObj();
            if (null != src && null != obj) {
                if (!src.getClass().equals(obj.getClass())) {
                    throw new Exception("对比的数据类型不匹配");
                }

                if (obj instanceof TData) {
                    mResult = compareData((DRef<?>) ((TData) src).get(), (DRef<?>) ((TData) obj).get());

                } else if (obj instanceof TArray) {
//                    mResult = compareArray(src, obj);

                } else if (obj instanceof DRef) {
                    mResult = compareData((DRef<?>) src, (DRef<?>) obj);
                }
                if (null != mResult) {
                    mResult.name.set(name);
                }
                return this;
            }
        }

        if (null != obj) {
            if (obj instanceof DRef) {
                mResult = getNew((DRef<?>) obj);

            } else if (obj instanceof TData) {
                mResult = getNew((DRef<?>) ((TData) obj).get());

            } else if (obj instanceof TArray) {
//                mResult = compareArray(src, obj);

            } else {
//                mResult = compareType(src, obj);
            }
            mResult.name.set(name);
            return this;
        }

        if (null == mStorage) {
            return this;
        }
        return this;
    }

    private Result getNew(DRef<?> obj) {
        ArrayList<Object> newValue = new ArrayList<>();
        Result res = null;
        Class<?> cla = obj.getClass();
        while (null != cla && cla != DRef.class) {
            for (Field field : cla.getDeclaredFields()) {
                field.setAccessible(true);
                try {
                    if (!isExtendsClass(field.getType(), com.hframe.type.T.class)) {
                        continue;
                    }

                    com.hframe.type.T<?> objVal = (com.hframe.type.T<?>) field.get(obj);
                    if (null == objVal) {
                        continue;
                    }

                    UseLogFiletr filetr = objVal.getfilter(UseLogFiletr.class);
                    if (null != filetr && (filetr.getReadWait() == UseLogFiletr.ReadWait.OnlyWait || filetr.getReadWait() == UseLogFiletr.ReadWait.NotReadWait)) {
                        continue;
                    }

                    String name = field.getName();
                    if (null != filetr && null != filetr.getName()) {
                        name = filetr.getName();
                    }

                    if (objVal instanceof TData) {
//                        res = compareData(srcVal., objVal);

                    } else if (objVal instanceof TArray) {
//                        res = compareArray(srcVal, objVal);

                    } else {
                        res = new Result();
                        res.name.set(name);
                        if (null != filetr) {
                            res.newValue.set(filetr.get());
                        } else {
                            res.newValue.set(objVal.get());
                        }
                        if (!res.newValue.isEmpty()) {
                            newValue.add(res);
                        }
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
            cla = cla.getSuperclass();
        }
        return getResult(null, newValue);
    }

    private Result compareType(com.hframe.type.T<?> src, com.hframe.type.T<?> obj) {
        if (!src.equals(obj)) {
            Result res = new Result();
            res.original.set(src);
            res.newValue.set(obj);
            return res;
        }
        return null;
    }

    private Result compareArray(com.hframe.type.T<?> src, com.hframe.type.T<?> obj) {
        ArrayList<Object> original = new ArrayList<>();
        ArrayList<Object> newValue = new ArrayList<>();
        try {
            TArray<Base> srcVal = (TArray<Base>) src;
            TArray<Base> objVal = coyp((TArray<Base>) obj);
            UseLogFiletr filetr = srcVal.getfilter(UseLogFiletr.class);
            if (null == filetr) {
                filetr = objVal.getfilter(UseLogFiletr.class);
            }

            Object srcKey;
            for (Base item : srcVal.get()) {
                srcKey = getIndexKey(filetr, item);
                if (null == srcKey) {
                    continue;
                }

                Base newObj = findNewObj(filetr, srcKey, objVal);
                if (null == newObj) {
                    original.add(new TData((DRef<?>) item, DRef.class));
                    newValue.add(null);
                } else {
                    Result res = compareData((DRef<?>) item, (DRef<?>) newObj);
                    if (null != res) {
                        original.add(res.original);
                        newValue.add(res.newValue);
                    }
                }
            }

            for (Base item : objVal.get()) {
                original.add(null);
                newValue.add(new TData((DRef<?>) item, DRef.class));
            }

        } catch (Exception e) {
            log.equals(e);
        }
        return getResult(original, newValue);
    }

    private TArray<Base> coyp(TArray<Base> obj) {
        TArray<Base> cop = new TArray<>(Base.class);
        for (Base item : obj.get()) {
            cop.add(item);
        }
        return cop;
    }

    private Result getResult(ArrayList original, ArrayList newValue) {
        Result res;
        if ((null != original && 0 < original.size()) || (null != newValue && 0 < newValue.size())) {
            return new Result(original, newValue);
        }
        return null;
    }

    private Base findNewObj(UseLogFiletr filetr, Object srcKey, TArray<Base> objVal) {
        for (Base item : objVal.get()) {
            Object indexKey = getIndexKey(filetr, item);
            if (null == srcKey) {
                continue;
            }

            if (indexKey.equals(srcKey)) {
                objVal.remove(item);
                return item;
            }
        }
        return null;
    }

    private Object getIndexKey(UseLogFiletr filetr, Base item) {
        if (null != filetr && null != filetr.getGetkey()) {
            return filetr.getGetkey().get(item);
        }

        if (item instanceof DRef) {
            Class<?> cla = item.getClass();
            while (null != cla && cla != DRef.class) {
                for (Field field : cla.getDeclaredFields()) {
                    field.setAccessible(true);
                    try {
                        if (!isExtendsClass(field.getType(), com.hframe.type.T.class)) {
                            continue;
                        }

                        return field.get(item);
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                }
                cla = cla.getSuperclass();
            }
        }

        return item;
    }

    private Result compareData(DRef<?> src, DRef<?> obj) {
        ArrayList<Object> newValue = new ArrayList<>();
        Result res = null;
        Class<?> cla = src.getClass();
        while (null != cla && cla != DRef.class) {
            for (Field field : cla.getDeclaredFields()) {
                field.setAccessible(true);
                try {
                    if (!isExtendsClass(field.getType(), com.hframe.type.T.class)) {
                        continue;
                    }

                    com.hframe.type.T<?> srcVal = (com.hframe.type.T<?>) field.get(src);
                    com.hframe.type.T<?> objVal = (com.hframe.type.T<?>) field.get(obj);
                    if (null == objVal || null == srcVal) {
                        continue;
                    }

                    UseLogFiletr filetr = objVal.getfilter(UseLogFiletr.class);
                    if (null != filetr && (filetr.getReadWait() == UseLogFiletr.ReadWait.OnlyWait || filetr.getReadWait() == UseLogFiletr.ReadWait.NotReadWait)) {
                        continue;
                    }

                    String name = field.getName();
                    if (null != filetr && null != filetr.getName()) {
                        name = filetr.getName();
                    }

                    if (objVal instanceof TData) {
//                        res = compareData(srcVal., objVal);

                    } else if (objVal instanceof TArray) {
                        res = compareArray(srcVal, objVal);

                    } else if (!srcVal.equals(objVal)) {
                        res = new Result();
                        if (null != filetr) {
                            res.newValue.set(filetr.get());
                        } else {
                            res.newValue.set(objVal.get());
                        }

                        filetr = srcVal.getfilter(UseLogFiletr.class);
                        if (null != filetr) {
                            res.original.set(filetr.get());
                        } else {
                            res.original.set(objVal.get());
                        }
                    }
                    if (null != res && (!res.original.isEmpty() || !res.newValue.isEmpty())) {
                        res.name.set(name);
                        newValue.add(res);
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
            cla = cla.getSuperclass();
        }
        return getResult(null, newValue);
    }


    protected static boolean isExtendsClass(Class<?> src, Class<?> dis) {
        while (src != null) {
            if (src == dis) {
                return true;
            }
            src = src.getSuperclass();
        }
        return false;
    }

    /**
     * 设置一个数据
     *
     * @param mData
     */
    public void setmData(Object mData) {
        this.mData = mData;
    }

    /**
     * 设置操作时间
     *
     * @param mDateTime
     */
    public void setDateTime(Date mDateTime) {
        this.mDateTime = mDateTime;
    }

    /**
     * 获得操作时间
     *
     * @return
     */
    public Date getDateTime() {
        return mDateTime;
    }

    /**
     * 对比结果
     */
    public static class Result extends DRef<Result> {
        protected final TString name = new TString();
        protected final com.hframe.type.T original = new com.hframe.type.T();
        protected final com.hframe.type.T newValue = new com.hframe.type.T();

        public Result() {
        }

        public Result(Object original, Object newValue) {
            this.original.set(original);
            this.newValue.set(newValue);
        }

        public Result(Object newValue) {
            this.newValue.set(newValue);
        }

        @Override
        public String toString() {
            StringBuffer buffer = new StringBuffer(name.get()).append(':');
            if (!original.isEmpty()) {
                buffer.append(original.toString());

                if (!newValue.isEmpty()) {
                    buffer.append("=>");
                }
            }

            if (!newValue.isEmpty()) {
                buffer.append(newValue.toString());
            }

            return buffer.toString();
        }
    }

    /**
     * 数据存储器
     *
     * @param <E>
     */
    public interface Storage<E> {
        public E getObj();
    }

    /**
     * 对像存储器
     *
     * @param <E>
     */
    public static class DataStorage<E> implements Storage<E> {
        private E data;

        public DataStorage(E data) {
            this.data = data;
        }

        @Override
        public E getObj() {
            return data;
        }
    }

    /**
     * Session存储器
     *
     * @param <E>
     */
    public static class SessionStorage<E> implements Storage<E> {
        private HttpSession session;
        private String key;

        public SessionStorage(HttpSession session, String key) {
            this.session = session;
            this.key = key;
        }

        @Override
        public E getObj() {
            if (null == session || null == key) {
                return null;
            }

            return (E) session.getAttribute("SessionStorage" + key);
        }

        public void save(E obj) {
            if (null == session || null == key) {
                return;
            }

            session.setAttribute("SessionStorage" + key, obj);
        }
    }


}
